Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | import { notFound } from 'next/navigation' import { canPerformAction } from '@/lib/classroom/access-control' import { getPracticeStudent } from '@/lib/curriculum/server' import { getUserId } from '@/lib/viewer' import { ReviewTimingsClient } from './ReviewTimingsClient' // Session/timing data must always be fresh (repairs self-correct at read time). export const dynamic = 'force-dynamic' interface ReviewTimingsPageProps { params: Promise<{ studentId: string }> searchParams: Promise<{ session?: string }> } /** * Timing Review Page — Server Component (#158). * * The single review destination (shared contract). Lists every flagged * (unusual/interrupted) timing across the student's recent sessions at the * player level, with per-attempt repair actions and a live estimate-recovery * header. Gated on `repair-data` (parent or teacher-present) to match the write * side — the whole page is a repair tool. * * URL: /practice/[studentId]/review-timings (optional ?session=<id> focus) */ export default async function ReviewTimingsPage({ params, searchParams }: ReviewTimingsPageProps) { const { studentId } = await params const { session } = await searchParams const player = await getPracticeStudent(studentId) if (!player) { notFound() } const viewerId = await getUserId() const canReview = await canPerformAction(viewerId, studentId, 'repair-data') if (!canReview) { // 404 rather than 403 to avoid leaking the player's existence. notFound() } return ( <ReviewTimingsClient studentId={studentId} playerName={player.name} playerEmoji={player.emoji} focusSessionId={session} /> ) } |